using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading;

namespace Microsoft.Coding4Fun.TweetCraft.Tray.Support
{
	/// <summary>
	/// Provides support for checking updates on the remote server. 
	/// </summary>
	public sealed class UpdateManager: IDisposable
	{
		// Name of the version file on the remote server
		private const string VersionFileName = "version.txt";
		// Name of the addon to look for in World of Warcraft
		private const string TweetCraftAddOnName = "TweetCraft";
		//Notifies the waiting thread that download has completed
		private static readonly AutoResetEvent UpdateCheckerResetEvent = new AutoResetEvent(false);
		private readonly IDownloadManager downloadManager;
		private readonly Uri updateServerUrl;

		/// <summary>
		/// Initializes a new instance of the <see cref="UpdateManager"/> class.
		/// </summary>
		/// <param name="downloadManager">The download manager instance.</param>
		/// <param name="updateServerUrl">The update server URL.</param>
		public UpdateManager(IDownloadManager downloadManager, Uri updateServerUrl)
		{
			if (updateServerUrl == null)
				throw new ArgumentNullException("updateServerUrl");

			if (downloadManager == null)
				throw new ArgumentNullException("downloadManager");

			this.downloadManager = downloadManager;
			this.updateServerUrl = updateServerUrl;
		}

		/// <summary>
		/// Blocks the current thread until the application update completed.
		/// </summary>
		public static void Block()
		{
			UpdateCheckerResetEvent.WaitOne();
		}

		/// <summary>
		/// Unblock the current thread.
		/// </summary>
		public static void Continue()
		{
			UpdateCheckerResetEvent.Set();
		}

		/// <summary>
		/// Checks for TweetCraft application updates.
		/// </summary>
		/// <param name="updateCompletedCallback">The update completed callback.</param>
		public void CheckForApplicationUpdates(Action<CheckUpdateResult> updateCompletedCallback)
		{
			if (updateCompletedCallback == null)
				throw new ArgumentNullException("updateCompletedCallback");

			Version currentVersion = GetApplicationVersionNumber();
			CheckForUpdates(currentVersion, updateCompletedCallback);
		}

		/// <summary>
		/// Checks for TweetCraft WoWAddOn updates.
		/// </summary>
		/// <param name="currentTweetCraftAddOnVersion">The current tweet craft addon version.</param>
		/// <param name="updateCompletedCallback">The update completed callback.</param>
		public void CheckForAddOnUpdates(Version currentTweetCraftAddOnVersion, Action<CheckUpdateResult> updateCompletedCallback)
		{
			if (updateCompletedCallback == null)
				throw new ArgumentNullException("updateCompletedCallback");
			if (currentTweetCraftAddOnVersion == null)
				throw new ArgumentNullException("currentTweetCraftAddOnVersion");

			CheckForUpdates(currentTweetCraftAddOnVersion, updateCompletedCallback);
		}

		/// <summary>
		/// Checks for updates on the remote server.
		/// </summary>
		/// <param name="currentVersion">The current version.</param>
		/// <param name="updateCompletedCallback">The update completed callback.</param>
		private void CheckForUpdates(Version currentVersion, Action<CheckUpdateResult> updateCompletedCallback)
		{
			if (updateCompletedCallback == null)
				throw new ArgumentNullException("updateCompletedCallback");
			if (currentVersion == null)
				throw new ArgumentNullException("currentVersion");

			var serverUri = new Uri(updateServerUrl, VersionFileName);
			downloadManager.DownloadFileAsString(serverUri,
												 (content, ex) =>
												 {
													 var updateResult = new CheckUpdateResult();
													 //Check passed exception
													 if (ex == null)
													 {
														 if (!String.IsNullOrEmpty(content))
														 {
															 var serverVersion = new Version(content);
															 updateResult.UpdateAvailable = serverVersion.Major > currentVersion.Major || (serverVersion.Major == currentVersion.Major && serverVersion.Minor > currentVersion.Minor);
															 updateResult.LocalVersion = currentVersion;
															 updateResult.ServerVersion = serverVersion;
														 }
														 else
														 {
															 Trace.TraceError("Could not get latest version number.");
														 }
													 }
													 else
													 {
														 updateResult.Success = false;
														 updateResult.Message = String.Format(CultureInfo.CurrentCulture, Strings.CouldNotDownloadFile, serverUri.AbsoluteUri);
													 }
													 updateCompletedCallback.Invoke(updateResult);
												 });
		}

		/// <summary>
		/// Gets the update from the server.
		/// </summary>
		/// <param name="updateFileName">The name of update file.</param>
		/// <param name="downloadCompletedCallback">The download completed callback.</param>
		public void GetUpdate(Uri updateFileName, Action<Stream> downloadCompletedCallback)
		{
			if (updateFileName == null)
				throw new ArgumentNullException("updateFileName");

			var serverUri = new Uri(updateServerUrl, updateFileName);
			//Download the content from the server and call the given callback after it has been completed
			downloadManager.DownloadFile(serverUri,(content, ex) => downloadCompletedCallback.Invoke(content));
		}

		/// <summary>
		/// Extracts the files.
		/// </summary>
		/// <param name="compressedFileStream">The compressed file stream.</param>
		/// <param name="addOnDirectoryName">Name of the AddOn directory.</param>
		public bool ExtractAndUpdateFiles(Stream compressedFileStream, string addOnDirectoryName)
		{
			if (addOnDirectoryName == null)
				throw new ArgumentNullException("addOnDirectoryName");
			if (compressedFileStream == null)
				throw new ArgumentNullException("compressedFileStream");

			if (Directory.Exists(addOnDirectoryName))
				Directory.CreateDirectory(addOnDirectoryName);

			//Get the user temporary directory
			string tempPath = Path.GetTempPath();
			string compressedFileName = Path.Combine(tempPath, "$TweetCraftAddOn.zip");
			string addOnTempFolder = Path.Combine(tempPath, "$TweetCraftAddOnUpdate\\");

			//Remove the content of the temp directory if already exists
			if (Directory.Exists(addOnTempFolder))
				Directory.Delete(addOnTempFolder, true);
			else
				Directory.CreateDirectory(addOnTempFolder);

			//Set the position of the stream back to 0.
			if (compressedFileStream.Position > 0)
				compressedFileStream.Position = 0;

			var length = (int) compressedFileStream.Length;
			var streamBuffer = new byte[length];
			compressedFileStream.Read(streamBuffer, 0, length);
			//Copy the downloaded zip content into a temporary file
			File.WriteAllBytes(compressedFileName, streamBuffer);
			//Uncompress the content of update to temp directory
			Zipper.Uncompress(compressedFileName, addOnTempFolder);

			string tempSubFolder = Path.Combine(addOnTempFolder, String.Concat(TweetCraftAddOnName, "\\"));
			if (!Directory.Exists(tempSubFolder))
				tempSubFolder = addOnTempFolder;

			//Updates the AddOn files in the WoW directory
			bool updateResult = UpgradeAddOn(tempSubFolder, addOnDirectoryName);
			//Clean temporary files and dierctories
			File.Delete(compressedFileName);
			Directory.Delete(addOnTempFolder, true);

			return updateResult;
		}

		/// <summary>
		/// Upgrade the AddOn to the new version.
		/// </summary>
		/// <param name="updatePath">The update path.</param>
		/// <param name="addOnDirectoryName">Name of the add on directory.</param>
		/// <returns></returns>
		private static bool UpgradeAddOn(string updatePath, string addOnDirectoryName)
		{
			if (addOnDirectoryName == null)
				throw new ArgumentNullException("addOnDirectoryName");
			if (updatePath == null)
				throw new ArgumentNullException("updatePath");

			if (!Directory.Exists(updatePath))
				return false;

			if (Directory.Exists(addOnDirectoryName))
				Directory.Delete(addOnDirectoryName, true);
            
			Directory.CreateDirectory(addOnDirectoryName);
			return CopyDirectory(updatePath, addOnDirectoryName, true);
		}

		/// <summary>
		/// Copies the given directory with its content into other directory.
		/// </summary>
		/// <param name="sourcePath">The source path.</param>
		/// <param name="destinationPath">The destination path.</param>
		/// <param name="overwrite">if set to <c>true</c> then overwrite existing content.</param>
		/// <returns></returns>
		private static bool CopyDirectory(string sourcePath, string destinationPath, bool overwrite)  
        {  
            bool result;  
            try  
            {
				sourcePath = sourcePath.EndsWith(@"\", StringComparison.CurrentCulture) ? sourcePath : sourcePath + @"\";
				destinationPath = destinationPath.EndsWith(@"\", StringComparison.CurrentCulture) ? destinationPath : destinationPath + @"\";  
   
                if (Directory.Exists(sourcePath))  
                {  
                    if (Directory.Exists(destinationPath) == false)  
                        Directory.CreateDirectory(destinationPath);  
   
                    foreach (string fileName in Directory.GetFiles(sourcePath))  
                    {  
                        var flinfo = new FileInfo(fileName);  
                        flinfo.CopyTo(destinationPath + flinfo.Name, overwrite);  
                    }  
                    foreach (string path in Directory.GetDirectories(sourcePath))  
                    {  
                        var drinfo = new DirectoryInfo(path);
                    	CopyDirectory(path, String.Concat(destinationPath, drinfo.Name), overwrite);
                    }  
                }  
                result = true;  
            }
			catch (IOException ex)  
            {
				Trace.TraceError(String.Format(CultureInfo.CurrentCulture, "Error copying AddOn: {0}", ex.Message));
                result = false;  
            }  
            return result;  
        }  

		/// <summary>
		/// Gets the assembly version number.
		/// </summary>
		/// <returns></returns>
		private static Version GetApplicationVersionNumber()
		{
			string name = Assembly.GetExecutingAssembly().FullName;
			var asmName = new AssemblyName(name ?? String.Empty);
			return asmName.Version;
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, 
		/// or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			//Release the blocked thread if exists.
			UpdateCheckerResetEvent.Set();
		}
	}
}